package math.petrinets.lpsolve;

import java.util.ArrayList;
import java.util.List;

import lpsolve.LpSolve;
import lpsolve.LpSolveException;
import math.petrinets.octave.OctaveUtils;

/**
 * A utility class to perform the simplex strategy using the LpSolve program.
 * 
 * This utility is designed to be used in a petri net context, so, the required
 * parameters are the ones used in the petri net world.
 * 
 * @author Rogelio Delgado González (roge.delgado@gmail.com)
 * 
 */
public class Simplex {

    /**
     * LpSolve method.
     * 
     * 
     * The method is intended to perform the linear programming problem:
     * 
     * max Yt C- V d
     * 
     * sa:
     *    Yt C = 0
     *    Tt M0 = 1
     *    Y >= 0
     * 
     * The functioning of the method is based in the following example:
     * 
     * 
     * try { // Create a problem with 4 variables and 0 constraints LpSolve
     * solver = LpSolve.makeLp(0, 5);
     * 
     * solver.setMaxim();
     * 
     * // add constraints solver.strAddConstraint("-1 1 1 0 0", LpSolve.EQ, 0);
     * solver.strAddConstraint("0 -1 0 1 0", LpSolve.EQ, 0);
     * solver.strAddConstraint("0 0 -1 0 1", LpSolve.EQ, 0);
     * solver.strAddConstraint("1 0 0 -1 -1", LpSolve.EQ, 0);
     * solver.strAddConstraint("1 0 0 0 0", LpSolve.EQ, 1);
     * 
     * // set objective function solver.strSetObjFn("1 2 3 1 1");
     * 
     * // solve the problem solver.solve();
     * 
     * // print solution double objective = solver.getObjective(); // delete the
     * problem and free memory solver.deleteLp();
     * 
     * return objective; } catch (LpSolveException e) { e.printStackTrace(); }
     * 
     * @param incidenceMatrix
     *            The incidence matrix of the petri net (C)
     * @param preMatrix
     *            The pre matrix of the petri net (C-)
     * @param initialMarker
     *            The initial marker of the petri net (M 0)
     * @param trigers
     *            The parik vector or triger matrix. (V)
     * @param transitionsWeight
     *            A vector that holds the weights of the transitions in the
     *            petri net.
     * @return
     * 
     * @throws LpSolveException
     */
    public double resolveSimplex(int[][] incidenceMatrix, int[][] preMatrix, int[] initialMarker, int[][] trigers, int[] transitionsWeight) throws LpSolveException {
	OctaveUtils octaveUtils = new OctaveUtils();
	// Using the octave utility we transpose the incidence matrix.
	int[][] transposeIncidence = octaveUtils.transposeMatrix("A", incidenceMatrix);
	int[][] constraints = addRowToMatrix(transposeIncidence, initialMarker);
	int[][] partial = octaveUtils.multiplyMatrixByMatrix("Pre", preMatrix, "V", trigers);
	int[] objectiveVector = octaveUtils.multiplyMatrixByVector("partial", partial, "d", transitionsWeight);
	String[] constraintsString = getConstraintStrings(constraints);
	String objective = getVectorString(objectiveVector);

	// Create a problem with 4 variables and 0 constraints
	LpSolve solver = LpSolve.makeLp(0, transposeIncidence[0].length);
	// Indicate that the operation to perform is  maximize.
	solver.setMaxim();
	// add constraints.
	for (int i = 0; i < constraintsString.length; i++) {
	    if (i == constraints.length - 1) {
		solver.strAddConstraint(constraintsString[i], LpSolve.EQ, 1);
	    } else {
		solver.strAddConstraint(constraintsString[i], LpSolve.EQ, 0);
	    }
	}
	// set objective function
	solver.strSetObjFn(objective);
	// solve the problem
	solver.solve();
	// print solution
	double res = solver.getObjective();
	// delete the problem and free memory
	solver.deleteLp();
	return res;
    }

    private int[][] addRowToMatrix(int[][] transposeIncidence, int[] initialMarker) {
	if (transposeIncidence[0].length != initialMarker.length) {
	    throw new IllegalArgumentException("Unable to add the row to the matrix: " + transposeIncidence[0] + " not compatible with " + initialMarker.length);
	}
	int[][] constraints = new int[transposeIncidence.length + 1][transposeIncidence[0].length];
	for (int i = 0; i < transposeIncidence.length; i++) {
	    constraints[i] = transposeIncidence[i].clone();
	}
	constraints[constraints.length - 1] = initialMarker.clone();
	return constraints;
    }

    private String getVectorString(int[] vector) {
	StringBuilder builder = new StringBuilder();
	for (int i = 0; i < vector.length; i++) {
	    builder.append(vector[i]);
	    if (i < vector.length - 1) {
		builder.append(" ");
	    }
	}
	return builder.toString();
    }

    private String[] getConstraintStrings(int[][] constraints) {
	List<String> list = new ArrayList<String>();
	for (int i = 0; i < constraints.length; i++) {
	    StringBuilder builder = new StringBuilder();
	    for (int j = 0; j < constraints[0].length; j++) {
		builder.append(constraints[i][j]);
		if (j < constraints[0].length - 1) {
		    builder.append(" ");
		}
	    }
	    list.add(builder.toString());
	}
	return (String[]) list.toArray(new String[0]);
    }
}
